/*
 * Copyright (c) 2002, 2010, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */

package com.sun.java.swing;

import sun.awt.EventQueueDelegate;
import sun.awt.AppContext;
import sun.awt.SunToolkit;

import java.util.Collections;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.concurrent.Callable;
import java.applet.Applet;
import java.awt.AWTEvent;
import java.awt.EventQueue;
import java.awt.Component;
import java.awt.Container;
import java.awt.Window;
import javax.swing.JComponent;
import javax.swing.RepaintManager;

/**
 * A collection of utility methods for Swing.
 * <p>
 * <b>WARNING:</b> While this class is public, it should not be treated as
 * public API and its API may change in incompatable ways between dot dot
 * releases and even patch releases. You should not rely on this class even
 * existing.
 *
 * This is a second part of sun.swing.SwingUtilities2. It is required
 * to provide services for JavaFX applets.
 */
public class SwingUtilities3 {

  /**
   * The {@code clientProperty} key for delegate {@code RepaintManager}
   */
  private static final Object DELEGATE_REPAINT_MANAGER_KEY =
      new StringBuilder("DelegateRepaintManagerKey");

  /**
   * Registers delegate RepaintManager for {@code JComponent}.
   */
  public static void setDelegateRepaintManager(JComponent component,
      RepaintManager repaintManager) {
        /* setting up flag in AppContext to speed up lookups in case
         * there are no delegate RepaintManagers used.
         */
    AppContext.getAppContext().put(DELEGATE_REPAINT_MANAGER_KEY,
        Boolean.TRUE);

    component.putClientProperty(DELEGATE_REPAINT_MANAGER_KEY,
        repaintManager);
  }

  private static final Map<Container, Boolean> vsyncedMap =
      Collections.synchronizedMap(new WeakHashMap<Container, Boolean>());

  /**
   * Sets vsyncRequested state for the {@code rootContainer}.  If
   * {@code isRequested} is {@code true} then vsynced
   * {@code BufferStrategy} is enabled for this {@code rootContainer}.
   *
   * Note: requesting vsynced painting does not guarantee one. The outcome
   * depends on current RepaintManager's RepaintManager.PaintManager
   * and on the capabilities of the graphics hardware/software and what not.
   *
   * @param rootContainer topmost container. Should be either {@code Window} or {@code Applet}
   * @param isRequested the value to set vsyncRequested state to
   */
  public static void setVsyncRequested(Container rootContainer,
      boolean isRequested) {
    assert (rootContainer instanceof Applet) || (rootContainer instanceof Window);
    if (isRequested) {
      vsyncedMap.put(rootContainer, Boolean.TRUE);
    } else {
      vsyncedMap.remove(rootContainer);
    }
  }

  /**
   * Checks if vsync painting is requested for {@code rootContainer}
   *
   * @param rootContainer topmost container. Should be either Window or Applet
   * @return {@code true} if vsync painting is requested for {@code rootContainer}
   */
  public static boolean isVsyncRequested(Container rootContainer) {
    assert (rootContainer instanceof Applet) || (rootContainer instanceof Window);
    return Boolean.TRUE == vsyncedMap.get(rootContainer);
  }

  /**
   * Returns delegate {@code RepaintManager} for {@code component} hierarchy.
   */
  public static RepaintManager getDelegateRepaintManager(Component
      component) {
    RepaintManager delegate = null;
    if (Boolean.TRUE == SunToolkit.targetToAppContext(component)
        .get(DELEGATE_REPAINT_MANAGER_KEY)) {
      while (delegate == null && component != null) {
        while (component != null
            && !(component instanceof JComponent)) {
          component = component.getParent();
        }
        if (component != null) {
          delegate = (RepaintManager)
              ((JComponent) component)
                  .getClientProperty(DELEGATE_REPAINT_MANAGER_KEY);
          component = component.getParent();
        }

      }
    }
    return delegate;
  }

  /*
   * We use maps to avoid reflection. Hopefully it should perform better
   * this way.
   */
  public static void setEventQueueDelegate(
      Map<String, Map<String, Object>> map) {
    EventQueueDelegate.setDelegate(new EventQueueDelegateFromMap(map));
  }

  private static class EventQueueDelegateFromMap
      implements EventQueueDelegate.Delegate {

    private final AWTEvent[] afterDispatchEventArgument;
    private final Object[] afterDispatchHandleArgument;
    private final Callable<Void> afterDispatchCallable;

    private final AWTEvent[] beforeDispatchEventArgument;
    private final Callable<Object> beforeDispatchCallable;

    private final EventQueue[] getNextEventEventQueueArgument;
    private final Callable<AWTEvent> getNextEventCallable;

    @SuppressWarnings("unchecked")
    public EventQueueDelegateFromMap(Map<String, Map<String, Object>> objectMap) {
      Map<String, Object> methodMap = objectMap.get("afterDispatch");
      afterDispatchEventArgument = (AWTEvent[]) methodMap.get("event");
      afterDispatchHandleArgument = (Object[]) methodMap.get("handle");
      afterDispatchCallable = (Callable<Void>) methodMap.get("method");

      methodMap = objectMap.get("beforeDispatch");
      beforeDispatchEventArgument = (AWTEvent[]) methodMap.get("event");
      beforeDispatchCallable = (Callable<Object>) methodMap.get("method");

      methodMap = objectMap.get("getNextEvent");
      getNextEventEventQueueArgument =
          (EventQueue[]) methodMap.get("eventQueue");
      getNextEventCallable = (Callable<AWTEvent>) methodMap.get("method");
    }

    @Override
    public void afterDispatch(AWTEvent event, Object handle) throws InterruptedException {
      afterDispatchEventArgument[0] = event;
      afterDispatchHandleArgument[0] = handle;
      try {
        afterDispatchCallable.call();
      } catch (InterruptedException e) {
        throw e;
      } catch (RuntimeException e) {
        throw e;
      } catch (Exception e) {
        throw new RuntimeException(e);
      }
    }

    @Override
    public Object beforeDispatch(AWTEvent event) throws InterruptedException {
      beforeDispatchEventArgument[0] = event;
      try {
        return beforeDispatchCallable.call();
      } catch (InterruptedException e) {
        throw e;
      } catch (RuntimeException e) {
        throw e;
      } catch (Exception e) {
        throw new RuntimeException(e);
      }
    }

    @Override
    public AWTEvent getNextEvent(EventQueue eventQueue) throws InterruptedException {
      getNextEventEventQueueArgument[0] = eventQueue;
      try {
        return getNextEventCallable.call();
      } catch (InterruptedException e) {
        throw e;
      } catch (RuntimeException e) {
        throw e;
      } catch (Exception e) {
        throw new RuntimeException(e);
      }
    }
  }
}
